#include "pch.h"
#include "Texture2D.h"
#include "Helper.h"
#include "Factories.h"
#include <memory>
#include "DXContext.h"
#include "..\DirectXTK\Inc\ScreenGrab.h"
#include "ErrorCodes.h"
#include "WIC.h"
#include "IOHelper.h"
#include "..\DirectXTK\Inc\DDSTextureLoader.h"

using namespace Platform;
using namespace DXBase;
using namespace DXBase::D3D;
using namespace Microsoft::WRL;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::Foundation;
using namespace concurrency;

Texture2D::Texture2D()
	: debug(L"Texture2D")
{
	watcher.Target = this;
	dpi = DPI::InchToDIP;
}
void Texture2D::LoadDXData()
{
	if (!Context)
		throw ExHelper::CreateException(ErrorCodes::ContextNotSet);
	if(!Context->ValidateDevice())
		ClearDXData();
	if (!ntexture)
	{
		if (loadDX._Empty())
			throw ExHelper::CreateException(ErrorCodes::TextureEmpty);
		loadDX(this);
	}
}
NativeTexture2D^ Texture2D::GetNativeTexture()
{
	LoadDXData();
	return ntexture;
}
NativeShaderResourceView^ Texture2D::GetNativeShaderResourceView()
{
	LoadDXData();
	if (!shaderResView)
	{
		auto tex = ntexture->texture;
		ComPtr<ID3D11ShaderResourceView> tv;
		CD3D11_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc(
			tex.Get(),
			D3D11_SRV_DIMENSION_TEXTURE2D
			);
		HR2Ex(Context->m_d3dDevice->CreateShaderResourceView(tex.Get(), &shaderResourceViewDesc, tv.GetAddressOf()));
		shaderResView = ref new NativeShaderResourceView(tv);
	}
	return shaderResView;
}
NativeResource^ Texture2D::GetNativeResource()
{
	LoadDXData();
	if (!nresource)
		nresource = ref new NativeResource(ntexture->texture);
	return nresource;
}

void Texture2D::LoadFile(Platform::String^ file)
{
	ClearDXData();
	loadDX = [file] (Texture2D^ me)
	{
		if (!me->Context)
			throw ExHelper::CreateException(ErrorCodes::ContextNotSet);

		ComPtr<ID3D11Texture2D> tex;
		auto bytes = IOHelper::ReadFile(file);
		if (GetFileExtension(file) == "dds")
		{
			ComPtr<ID3D11Resource> resource;
            DirectX::CreateDDSTextureFromMemory(
                me->Context->m_d3dDevice.Get(),
				bytes->Data,
				bytes->Length,
				&resource,
				NULL
                );
			HR2Ex(resource.As(&tex));
		}
		else
		{
			auto pConv = WIC::LoadImage(bytes);
			uint32 width;
			uint32 height;
			HR2Ex(pConv->GetSize(&width, &height));
			std::unique_ptr<byte[]> pixels(new byte[width * height * 4]);
			HR2Ex(pConv->CopyPixels(nullptr, width * 4, width * height * 4, pixels.get()));
			me->CreateBuffer(tex, width, height, &pixels[0]);
		}
		me->ntexture = ref new NativeTexture2D(tex);
	};
}
void Texture2D::Create(UINT width, UINT height)
{
	ClearDXData();
	loadDX = [width,height] (Texture2D^ me)
	{
		if (!me->Context)
			throw ExHelper::CreateException(ErrorCodes::ContextNotSet);

		ComPtr<ID3D11Texture2D> tex;
		me->CreateBuffer(tex, width, height, nullptr);
		me->ntexture = ref new NativeTexture2D(tex);
	};
}
void Texture2D::CreateBuffer(ComPtr<ID3D11Texture2D>& tex, uint32 w, uint32 h, byte* initData)
{
	CD3D11_TEXTURE2D_DESC textureDesc(
		DXGI_FORMAT_B8G8R8A8_UNORM,
		w,   // Width
		h,  // Height
		1,       // MipLevels
		1,       // ArraySize
		D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET
		);
	D3D11_SUBRESOURCE_DATA initialData = { initData, w * 4, 0 };
	HR2Ex(Context->m_d3dDevice->CreateTexture2D(
		&textureDesc, 
		initData ? &initialData : nullptr, 
		&tex));
}

void Texture2D::InitCopy(Microsoft::WRL::ComPtr<ID3D11Texture2D> tex)
{
	CD3D11_TEXTURE2D_DESC desc;
	tex->GetDesc(&desc);

	Create(desc.Width, desc.Height);
	LoadDXData();
	Context->m_d3dContext->CopyResource(ntexture->texture.Get(), tex.Get());
}

Windows::Foundation::Size Texture2D::Size::get()
{
	return DPI::GetDIP(Dpi, PixelSize);
}
USize Texture2D::PixelSize::get()
{
	if (!ntexture)
		return makeUSize(0, 0);
	CD3D11_TEXTURE2D_DESC desc;
	ntexture->texture->GetDesc(&desc);
	return makeUSize(desc.Width, desc.Height);
}

static GUID GetWICFormat(DXGI_FORMAT);
static GUID GetWICFormat(ImageFileFormat);
static ComPtr<ID3D11Texture2D> StagingCopy(DXContext^ ctxt, ComPtr<ID3D11Texture2D> src);
void Texture2D::Save(Windows::Storage::Streams::IRandomAccessStream^ dst, ImageFileFormat fmt)
{
	// TODO move some code in WIC
	LoadDXData();
	auto texture = GetNativeTexture()->texture;

    ComPtr<IStream> stream;
	HR2Ex(CreateStreamOverRandomAccessStream(dst, IID_PPV_ARGS(&stream)));

	CD3D11_TEXTURE2D_DESC desc;
	texture->GetDesc(&desc);

	auto pStaging = StagingCopy(Context, texture);
	Mapped<ID3D11Texture2D> staging;
	staging.Initialize(Context, pStaging);

	ComPtr<IWICBitmapEncoder> encoder;
	HR2Ex(Factories::GetWicFactory()->CreateEncoder(GetWICFormat(fmt), 0, &encoder));
	HR2Ex(encoder->Initialize(stream.Get(), WICBitmapEncoderNoCache));

	ComPtr<IWICBitmapFrameEncode> frame;
	HR2Ex(encoder->CreateNewFrame(&frame, nullptr));
	HR2Ex(frame->Initialize(nullptr));
	HR2Ex(frame->SetSize(desc.Width, desc.Height));
	HR2Ex(frame->SetResolution(Dpi, Dpi));
	HR2Ex(frame->SetPixelFormat(&GetWICFormat(fmt)));

	staging.Map();

	ComPtr<IWICBitmap> source;
	HR2Ex(Factories::GetWicFactory()->CreateBitmapFromMemory( 
		desc.Width, desc.Height, 
		GetWICFormat(desc.Format),
		staging.mapped.RowPitch, staging.mapped.RowPitch * desc.Height,
		reinterpret_cast<BYTE*>( staging.mapped.pData ), 
		&source));

	ComPtr<IWICFormatConverter> FC;
	HR2Ex(Factories::GetWicFactory()->CreateFormatConverter( &FC ));
	HR2Ex(FC->Initialize( source.Get(), GetWICFormat(desc.Format), WICBitmapDitherTypeNone, 0, 0, WICBitmapPaletteTypeCustom ));
	WICRect rect = { 0, 0, desc.Width, desc.Height };
	frame->WriteSource( FC.Get(), &rect );

	frame->Commit();
	encoder->Commit();
}
static ComPtr<ID3D11Texture2D> StagingCopy(DXContext^ ctxt, ComPtr<ID3D11Texture2D> src)
{
	ComPtr<ID3D11Texture2D> pStaging;

	D3D11_TEXTURE2D_DESC desc;
	src->GetDesc(&desc);
	desc.BindFlags = 0;
	desc.MiscFlags &= D3D11_RESOURCE_MISC_TEXTURECUBE;
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
	desc.Usage = D3D11_USAGE_STAGING;

	HR2Ex(ctxt->m_d3dDevice->CreateTexture2D( &desc, 0, pStaging.GetAddressOf()));
	assert(pStaging.Get());

	ctxt->m_d3dContext->CopyResource(pStaging.Get(), src.Get());

	return pStaging;
}
static GUID GetWICFormat(ImageFileFormat fmt)
{
	switch (fmt)
	{
	case ImageFileFormat::BMP: return GUID_ContainerFormatBmp;
	case ImageFileFormat::JPG: return GUID_ContainerFormatJpeg;
	case ImageFileFormat::PNG: return GUID_ContainerFormatPng;
	default:
		throw ref new InvalidArgumentException();
	}
}
static GUID GetWICFormat(DXGI_FORMAT fmt)
{
    switch ( fmt )
    {
    case DXGI_FORMAT_R32G32B32A32_FLOAT:            return GUID_WICPixelFormat128bppRGBAFloat; break;
    case DXGI_FORMAT_R16G16B16A16_FLOAT:            return GUID_WICPixelFormat64bppRGBAHalf; break;
    case DXGI_FORMAT_R16G16B16A16_UNORM:            return GUID_WICPixelFormat64bppRGBA; break;
    case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM:    return GUID_WICPixelFormat32bppRGBA1010102XR; break; // DXGI 1.1
    case DXGI_FORMAT_R10G10B10A2_UNORM:             return GUID_WICPixelFormat32bppRGBA1010102; break;
    case DXGI_FORMAT_R9G9B9E5_SHAREDEXP:            return GUID_WICPixelFormat32bppRGBE; break;
    case DXGI_FORMAT_B5G5R5A1_UNORM:                return GUID_WICPixelFormat16bppBGRA5551; break;
    case DXGI_FORMAT_B5G6R5_UNORM:                  return GUID_WICPixelFormat16bppBGR565; break;
    case DXGI_FORMAT_R32_FLOAT:                     return GUID_WICPixelFormat32bppGrayFloat; break;
    case DXGI_FORMAT_R16_FLOAT:                     return GUID_WICPixelFormat16bppGrayHalf; break;
    case DXGI_FORMAT_R16_UNORM:                     return GUID_WICPixelFormat16bppGray; break;
    case DXGI_FORMAT_R8_UNORM:                      return GUID_WICPixelFormat8bppGray; break;
    case DXGI_FORMAT_A8_UNORM:                      return GUID_WICPixelFormat8bppAlpha; break;

    case DXGI_FORMAT_R8G8B8A8_UNORM:
    case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
        return GUID_WICPixelFormat32bppRGBA;
        break;

    case DXGI_FORMAT_B8G8R8A8_UNORM: // DXGI 1.1
    case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
        return GUID_WICPixelFormat32bppBGRA;
        break;

    case DXGI_FORMAT_B8G8R8X8_UNORM: // DXGI 1.1
    case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
        return GUID_WICPixelFormat32bppBGR;
        break; 

    default:
		throw ref new InvalidArgumentException();
    }
}